package nba2kmodtool.filedesc;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.List;

import nba2kmodtool.util.ByteFileReader;

/**
 * IffFile 基类
 * @author JemiZhuu
 * @category BaseIffFile
 * @version 1.0
 * @since 2010-12-21
 */
public abstract class BaseIffFile {

	private static final float VERSION = 0.198F;
	/**Texture File Type*/
	public static final long TYPE_TEXTURE = 0x5C369069L;
	/**Package File Type*/
	public static final long TYPE_PACKAGE = 0xE26C9B5DL;
	public static long PACKAGE_TYPE = 0xE26C9B5DL;
	protected long fileType;
	protected boolean isIndex = false;
	protected int fileBlockNum;
	protected long fileSize;
	protected int fileNum;
	protected boolean isCompressed = false;
	protected IffBlock[] blocks;
	protected IffDataFile[] dataFiles;
	protected int headerSize;
	protected int indexedBlock = -1;
	protected static final boolean IS_DEBUG = false;
	protected File file;
	protected N2kModelPartReader modelPartReader;

	protected File cdfFile;

	public static float getVersion() {
		return VERSION;
	}

	protected BaseIffFile(File fileIn) throws IOException {
		this.file = fileIn;
		ByteFileReader in = new ByteFileReader(file, "r");
		//--
		this.fileType = in.read4Bytes();
		if (fileType == 0xFF3BEF94L) {
			new N2kIffFileReader(this).read(in);
		} else if (fileType == 0xF0985030L) {
			new N2kCdfFileReader(this).read(in);
		} else {
			in.close();
			throw new IOException("Unsuport IFF File: " + file.getPath());
		}
		in.close();
		this.modelPartReader = getN2kModelPartReader();
	}

	protected List<Integer> resolveIg3DModelFaceData(N2k3DModelPart part, ByteFileReader in) throws IOException {
		return modelPartReader.resolveIg3DModelFaceData(part, in);
	}

	/**
	 * Export Model to IG/?
	 * @param outfile
	 * @param models
	 * @throws IOException
	 */
	public void exportIgModel(File outfile, N2k3DModelPart[] models) throws IOException {
		new N2kIgModelExporter(this).exportIgModel(outfile, models);
	}

	/**
	 * Export Model to N2KM File
	 * @param outfile
	 * @param models
	 * @throws IOException
	 */
	public void exportModelFile(File outfile, N2k3DModelPart[] models) throws IOException {
		new N2kModelExporter(this).exportModelFile(outfile, models);
	}

	/**
	 * Export Model to N2KM File/?
	 * @param outfile
	 * @param models
	 * @throws IOException
	 */
	public void exportModelFile0(File outfile, N2k3DModelPart[] models) throws IOException {
		new N2kModelExporter(this).exportModelFile0(outfile, models);
	}

	/**
	 * Export Model to OBJ File
	 * @param outfile
	 * @param models
	 * @throws IOException
	 */
	public void exportModelToOBJFile(File outfile, N2k3DModelPart[] models) throws IOException {
		new N2kModelObjFileExporter(this).exportModelToOBJFile(outfile, models);
	}

	/**
	 * Export Sub File
	 * @param target
	 * @param outfile
	 * @throws IOException
	 */
	public void exportSubFile(N2kTextureFile target, File outfile) throws IOException {
		ByteFileReader in = new ByteFileReader(file, "r");
		ByteFileReader out = new ByteFileReader(outfile, "rw");
		//write header
		ByteBuffer buff = ByteBuffer.allocate(0xF0);
		in.seek(target.headerStart);
		in.read(buff.array());
		out.write(buff.array());
		//write data
		buff.clear();
		buff = ByteBuffer.allocate((int) target.dataSize);
		in.seek(target.dataStart);
		in.read(buff.array());
		out.write(buff.array());
		in.close();
		out.setLength(out.getFilePointer());
		out.close();
	}

	public void exportTextureFile(File outfile, N2kTextureFile target) throws IOException {
		new N2kTextureExporter(this).exportTextureFile(outfile, target);
	}

	public IffBlock[] getBlocks() {
		return blocks;
	}

	public List<IntBuffer> getChildModelFaces(N2k3DModelPart model, ByteFileReader in) throws IOException {
		return resolve3DModelFaceData(model, in);
	}

	public List<FloatBuffer> getChildModelVertets(N2k3DModelPart model, ByteFileReader in) throws IOException {
		return modelPartReader.resolve3DModelVertexData(model, in);
	}

	public IffDataFile[] getDataFiles() {
		return dataFiles;
	}

	public File getFile() {
		return file;
	}

	public int getFileBlockNum() {
		return fileBlockNum;
	}

	public int getFileNum() {
		return fileNum;
	}

	public long getFileSize() {
		return fileSize;
	}

	public long getFileType() {
		return fileType;
	}

	public int getHeaderSize() {
		return headerSize;
	}

	public boolean importIffModel(BaseIffFile impiff, N2kPackageFile imppack, int impindex, N2k3DModelPart model)
			throws IOException {
		return new N2kIffFileModelImporter(this).importIffModel(impiff, imppack, impindex, model);
	}

	/**
	 * Import N2KM File Model
	 * @param modelfile
	 * @param pagFile
	 * @return
	 * @throws IOException
	 */
	public boolean importModel(File modelfile, N2kPackageFile pagFile, int importMode) throws IOException {
		if (importMode != N2kModelPartImporter.IMPORT_ALL && importMode != N2kModelPartImporter.IMPORT_XYZ) {
			throw new IOException("Import Mode invalid:" + importMode);
		}
		new N2kModelPartImporter(this).importModel(modelfile, pagFile, importMode);
		return true;
	}

	public void importTextureFile(File impfile, N2kTextureFile target) throws IOException {
		new N2kTextureImporter(this).importTextureFile(impfile, target);
	}

	public boolean isCompressed() {
		return isCompressed;
	}

	/**
	 * Read Texture
	 * @param src
	 * @return
	 * @throws IOException
	 */
	public BufferedImage readN2KTexture(N2kTextureFile src) throws IOException {
		return new N2kTextureReader(this).readN2KTexture(src);
	}

	/**
	 * Read Package File
	 * @param index
	 * @return
	 * @throws IOException
	 */
	public N2kPackageFile readPackageFile(int index) throws IOException {
		return new N2kPackageFileReader(this).readPackageFile(index);
	}

	/**
	 * Read Texture File
	 * @param index
	 * @return
	 * @throws IOException
	 */
	public N2kTextureFile readTextureFile(int index) throws IOException {
		return new N2kTextureReader(this).readTextureFile(index);
	}

	/**
	 * 解析3D模型面索引数据
	 * Resolve Face Data(needBase whether need sub BaseIndex for child model--if export to .OBJ file, needBase=false)
	 * @param model
	 * @param in
	 * @param needBase
	 * @return
	 * @throws IOException
	 */
	protected List<IntBuffer> resolve3DModelFaceData(N2k3DModelPart model, ByteFileReader in, boolean... needBase)
			throws IOException {
		return modelPartReader.resolve3DModelFaceData(model, in, needBase);
	}

	protected N2kModelPartReader getN2kModelPartReader() {
		return new N2kModelPartReader(this);
	}

	public boolean writeDecompressFile() throws IOException {
		ByteFileReader in = new ByteFileReader(file, "r");
		if (!isCompressed) {
			return true;
		}
		File outfile = new File(file.getPath() + ".temp");
		ByteFileReader out = new ByteFileReader(outfile, "rw");
		if (fileType == 0xFF3BEF94L) {
			new N2kIffFileDecompress(this).writeDecompressFile(in, out);
		} else if (fileType == 0xF0985030L) {
			new N2kCdfFileDecompress(this).writeDecompressFile(in, out);
		} else {
			in.close();
			throw new IOException("UnSupport IFF File: " + file.getPath());
		}
		in.close();
		out.close();
		File bakFile = new File(file.getPath() + ".bak");
		if (bakFile.exists()) {
			bakFile.delete();
		}
		if (!file.renameTo(bakFile)) {
			throw new IOException("Can`t Rename Temp File");
		}
		outfile.renameTo(file);
		return true;
	}

	public boolean writeIndexExportFile(File outfile, int idx) throws IOException {
		ByteFileReader in = null;
		if (fileType == 0xFF3BEF94L) {
			in = new ByteFileReader(file, "r");
		} else if (fileType == 0xF0985030L) {
			in = new ByteFileReader(cdfFile, "r");
		}
		ByteFileReader out = new ByteFileReader(outfile, "rw");
		IffDataFile dataFile = this.dataFiles[idx];
		int num = dataFile.partNum;
		ByteBuffer buff;
		if (dataFile.indexType <= 0) {
			for (int i = 0; i < num; i++) {
				long length = dataFile.partSize[i];
				long addr = dataFile.partAddr[i];
				if (IS_DEBUG) {
					System.out.printf("Export Bin-%d: %X + %X = %X\n", idx, addr, length, (addr + length));
				}
				buff = ByteBuffer.allocate((int) length);
				in.seek(addr);
				in.read(buff.array());
				out.write(buff.array());
				buff.clear();
			}
			in.close();
		} else {
			//write header
			long length = dataFile.partSize[0];
			long addr = dataFile.partAddr[0];
			buff = ByteBuffer.allocate((int) length);
			in.seek(addr);
			in.read(buff.array());
			out.write(buff.array());
			buff.clear();
			//write data chunk
			IffBlockIndex index = dataFile.index;
			int count = index.getIndexCount();
			int total = 0;
			for (int i = count - 1; i > 0; i--) {
				total += index.getSize(i);
			}
			buff = ByteBuffer.allocate(total);
			int writeCount = 0;
			for (int i = count - 1; i > 0; i--) {
				addr = index.getAddr(i);
				length = index.getSize(i);
				in.seek(addr);
				if (IS_DEBUG) {
					System.out.printf("Export Bin-%d: %X + %X = %X\n", idx, addr, length, (addr + length));
				}
				in.read(buff.array(), writeCount, (int) length);
				writeCount += length;
			}
			out.write(buff.array());
		}
		out.setLength(out.getFilePointer());
		out.close();
		return true;
	}
}